/*
 *------------------------------------------------------------------------------
 *
 *                      66.20 - ORGANIZACION DE COMPUTADORAS
 *                      TP1: Conjunto de instrucciones MIPS
 *
 *------------------------------------------------------------------------------
 *
 * Grupo:
 * 79979 - Gonzalez, Juan Manuel (juan0511@yahoo.com)
 * 80560 - Kux, Daniel (danielkux@gmail.com)
 * 89918 - Morales, Nahuel (nahuel.leandro@gmail.com)
 *
 * Fecha de entrega: 02/05/2013 - Fecha de re-entrega: 16/05/2013
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>

#include "bubblesort.h"
#include "shellsort.h"

/* Definicion de simbolos */
#define OP_BUBBLESORT 0
#define OP_SHELLSORT 1

#define MAX_WORD_SIZE 256

typedef char buffer_t;

/* Funcion que imprime la ayuda */
void imprimir_ayuda (FILE* stream, int exit_code){

  fprintf (stream,
		"tp1 [OPTIONS] [file...]\n"
		"-h, --help\t display this help and exit.\n"
		"-V, --version\t display version information and exit.\n"
		"-m, --bubble\t use bubblesort algorithm.\n"
		"-s, --sel\t use shellsort algorithm.\n"
		"\n");
		
  exit (exit_code);
}

/* Funcion que imprime la version del programa */
void imprimir_version(FILE* stream, int exit_code){

	fprintf (stream, "TP1: v1.0.\n");
	exit (exit_code);
}

/* Funcion que imprime el complemento de los mensajes de error */
void imprimir_leer_mas(FILE* stream, int exit_code){

	fprintf (stream, "Try `tp1 --help' for more information.\n");
	exit (exit_code);
}

/* Devuelve 1 si el archivo es regular, de otro modo devuelve 0 */
int isRegular(const char* filename) {
	struct stat fileStatus;
	if (stat(filename,&fileStatus) != 0) {
		return 0;
	}
	return S_ISREG(fileStatus.st_mode);
}

/* Words Generator */
int readWords(FILE *file, buffer_t ***wordsBuffer, int *arraysize) {
	
	buffer_t auxBuffer[MAX_WORD_SIZE];
	int currOffset = 0;
	buffer_t c = ' ';
	
	buffer_t *word = NULL;

	int error = 0;
	int returnCode = EXIT_SUCCESS;

	while (!feof(file)) {
		error = fscanf(file, "%c", &c);

		if ((error != 1) && (!feof(file))) {
			fprintf(stderr, "tp1: error processing input.\n");
			returnCode = EXIT_FAILURE;
			break;
		} 
		
		if ((isalpha((int)c)) && (!feof(file))) { /* Si no hay cambio de palabra acumulo las letras */

			auxBuffer[currOffset] = c;
			currOffset ++;
			
		} else if (currOffset > 0) { /* Si la palabra tiene longitud la guardo */
			
			auxBuffer[currOffset] = '\0';
			currOffset ++;

			word = (buffer_t *)malloc(sizeof(buffer_t) * currOffset);
			if (word == NULL) {
				fprintf(stderr, "tp1: memory error processing input.\n");
				returnCode = EXIT_FAILURE;
				break;
			}

			memcpy(word, auxBuffer, currOffset);

			*arraysize = *arraysize + 1;
			if (*wordsBuffer == NULL) {
				*wordsBuffer = (buffer_t **)malloc(sizeof(buffer_t **) * (*arraysize));
			} else {
				*wordsBuffer = (buffer_t **)realloc(*wordsBuffer, sizeof(buffer_t **) * (*arraysize));
			}

			if (word == NULL) {
				free(word);
				fprintf(stderr, "tp1: memory error processing input.\n");
				returnCode = EXIT_FAILURE;
				break;
			}

			(*wordsBuffer)[(*arraysize) -1] = word;
			currOffset = 0;
		}
	}

	return returnCode;
}

int validateFileAndReadWords(char *filename, buffer_t ***wordsBuffer, int *arraysize) {
	FILE* file = NULL;
	int returnCode = EXIT_SUCCESS;

	if(isRegular(filename)) {
		file = fopen(filename, "rb");
		if(file != NULL) {
			returnCode = readWords(file, wordsBuffer, arraysize);	
			fclose(file);
		} else {
			fprintf(stderr, "tp1: could not read input file `%s'.\n",filename);
			returnCode = EXIT_FAILURE;
		}	
	} else {
			fprintf(stderr, "tp1: invalid input file `%s'.\n",filename);
			returnCode = EXIT_FAILURE;
	}

	return returnCode;
}

int readFromStdinAndReadWords(buffer_t ***wordsBuffer, int *arraysize) {
	
	FILE * file = stdin;
	return readWords(file, wordsBuffer, arraysize);
}

/* Funcion principal */
int main(int argc, char* argv[]) {
	
	int i = 0;
	int returnCode = EXIT_SUCCESS;
	int auxCode = 0;
	int opcion=0;

	/* Cantidad de archivos */
	int filesCount = 0;
	char *filename = NULL;

	/* Operation */
	int opType = OP_SHELLSORT;

	/* Words Buffer */
	buffer_t **wordsBuffer = NULL;
	int arraysize = 0;

	const char* const short_options = "Vhms";
	const struct option long_options[] =
	{
		{ "version", 0, NULL, 'V' },
		{ "help", 0, NULL, 'h' },
		{ "bubble", 0, NULL, 'm' },
		{ "sel", 0, NULL, 's' },
		{ NULL, 0, NULL, 0 },
	};

	/* Comienzo de la validacion de argumentos */
	while((opcion = getopt_long (argc, argv, short_options, long_options, NULL)) != -1) {

		switch (opcion)
		{
		    case 'h':   /* -h o --help */
			    	imprimir_ayuda(stdout, EXIT_SUCCESS);
		    		break;
		    case 'V':   /* -V o --version */
				   	imprimir_version(stdout, EXIT_SUCCESS);
				   	break;
		    case 'm':   /* -m o --bubble */
					opType = OP_BUBBLESORT;
			    	break;
		    case 's':   /* -s o --sel */
					opType = OP_SHELLSORT;
			    	break;
		    case '?':  /* argumento no valido */
			    	imprimir_leer_mas(stderr, EXIT_FAILURE);
				break;
		    default:
				abort();
		}
	}

	/* Localizacion de archivos */
	filesCount = argc-optind;

	/* Procesamiento de archivos o stdin */
	if(filesCount > 0) {
		for(i = 0; i < filesCount; i++) {
			filename = argv[optind+i];
			auxCode = validateFileAndReadWords(filename, &wordsBuffer, &arraysize);
			if (auxCode != EXIT_SUCCESS) {
				returnCode = EXIT_FAILURE;
				free(wordsBuffer);
				return returnCode;
			}	
		}
	} else {
		auxCode = readFromStdinAndReadWords(&wordsBuffer, &arraysize);
		if (auxCode != EXIT_SUCCESS) {
			returnCode = EXIT_FAILURE;
			free(wordsBuffer);
			return returnCode;
		}
	}

	/* Sorting */
	if (opType == OP_BUBBLESORT)
		bubblesort(wordsBuffer, arraysize);
	else
		shellsort(wordsBuffer, arraysize);

	/* Imprimir palabras */
	for(i = 0; i < arraysize; i++)
		fprintf(stdout, "%s ", wordsBuffer[i]);
	fprintf(stdout, "\n");

	/* Liberar recursos */
	for(i = 0; i < arraysize; i++) {
		free(wordsBuffer[i]);
	}
	free(wordsBuffer);

	return returnCode;
}
